home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / JFC.bin / UndoManager.java < prev    next >
Text File  |  1998-06-30  |  14KB  |  440 lines

  1. /*
  2.  * @(#)UndoManager.java    1.15 98/02/16
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20.  
  21. package com.sun.java.swing.undo;
  22.  
  23. import com.sun.java.swing.event.*;
  24. import java.util.*;
  25.  
  26. /**
  27.  * Concrete subclass of CompoundEdit which can serve as an
  28.  * UndoableEditListener, consolidating the UndoableEditEvents from a
  29.  * variety of sources, and undoing or redoing them one at a time.
  30.  *
  31.  * Unlike AbstractUndoableEdit and CompoundEdit, the public methods of this
  32.  * class are synchronized, and should be safe to call from multiple
  33.  * threads. This should make UndoManager a convenient marshall for
  34.  * sets of undoable JavaBeans.
  35.  * <p>
  36.  * Warning: serialized objects of this class will not be compatible with
  37.  * future swing releases.  The current serialization support is appropriate
  38.  * for short term storage or RMI between Swing1.0 applications.  It will
  39.  * not be possible to load serialized Swing1.0 objects with future releases
  40.  * of Swing.  The JDK1.2 release of Swing will be the compatibility
  41.  * baseline for the serialized form of Swing objects.
  42.  *
  43.  * @author Ray Ryan
  44.  * @version 1.15, 02/16/98
  45.  */ 
  46. public class UndoManager extends CompoundEdit implements UndoableEditListener {
  47.     int indexOfNextAdd;
  48.     int limit;
  49.  
  50.     public UndoManager() {
  51.         super();
  52.         indexOfNextAdd = 0;
  53.         limit = 100;
  54.         edits.ensureCapacity(limit);
  55.     }
  56.  
  57.     /**
  58.      * Returns the maximum number of edits this UndoManager will
  59.      * hold. Default value is 100.
  60.      *
  61.      * @see #addEdit
  62.      * @see #setLimit
  63.      */
  64.     public synchronized int getLimit() {
  65.         return limit;
  66.     }
  67.      
  68.     /**
  69.      * Empty the undo manager, sending each edit a die message
  70.      * in the process.
  71.      */
  72.     public synchronized void discardAllEdits() {
  73.         Enumeration cursor = edits.elements();
  74.         while (cursor.hasMoreElements()) {
  75.             UndoableEdit e = (UndoableEdit)cursor.nextElement();
  76.             e.die();
  77.         }
  78.         edits = new Vector(limit);
  79.         indexOfNextAdd = 0;
  80.         // PENDING(rjrjr) when vector grows a removeRange() method
  81.         // (expected in JDK 1.2), trimEdits() will be nice and
  82.         // efficient, and this method can call that instead.
  83.     }
  84.  
  85.     /**
  86.      * Reduce the number of queued edits to a range of size limit,
  87.      * centered on indexOfNextAdd.  
  88.      */
  89.     protected void trimForLimit() {
  90.         if (limit > 0) {
  91.             int size = edits.size();
  92. //          System.out.print("limit: " + limit +
  93. //                           " size: " + size +
  94. //                           " indexOfNextAdd: " + indexOfNextAdd +
  95. //                           "\n");
  96.         
  97.             if (size > limit) {
  98.                 int halfLimit = limit/2;
  99.                 int keepFrom = indexOfNextAdd - 1 - halfLimit;
  100.                 int keepTo   = indexOfNextAdd - 1 + halfLimit;
  101.  
  102.                 // These are ints we're playing with, so dividing by two
  103.                 // rounds down for odd numbers, so make sure the limit was
  104.                 // honored properly. Note that the keep range is
  105.                 // inclusive.
  106.  
  107.                 if (keepTo - keepFrom + 1 > limit) {
  108.                     keepFrom++;
  109.                 }
  110.  
  111.                 // The keep range is centered on indexOfNextAdd,
  112.                 // but odds are good that the actual edits Vector
  113.                 // isn't. Move the keep range to keep it legal.
  114.  
  115.                 if (keepFrom < 0) {
  116.                     keepTo -= keepFrom;
  117.                     keepFrom = 0;
  118.                 }
  119.                 if (keepTo >= size) {
  120.                     int delta = size - keepTo - 1;
  121.                     keepTo += delta;
  122.                     keepFrom += delta;
  123.                 }
  124.  
  125. //              System.out.println("Keeping " + keepFrom + " " + keepTo);
  126.                 trimEdits(keepTo+1, size-1);
  127.                 trimEdits(0, keepFrom-1);
  128.             }
  129.         }
  130.     }
  131.         
  132.     /**
  133.      * Tell the edits in the given range (inclusive) to die, and
  134.      * remove them from edits. from > to is a no-op. 
  135.      */
  136.     protected void trimEdits(int from, int to) {
  137.         if (from <= to) {
  138. //          System.out.println("Trimming " + from + " " + to + " with index " +
  139. //                           indexOfNextAdd);
  140.             for (int i = to; from <= i; i--) {
  141.                 UndoableEdit e = (UndoableEdit)edits.elementAt(i);
  142. //              System.out.println("JUM: Discarding " +
  143. //                                 e.getUndoPresentationName());
  144.                 e.die();
  145.                 // PENDING(rjrjr) when Vector supports range deletion (JDK
  146.                 // 1.2) , we can optimize the next line considerably. 
  147.                 edits.removeElementAt(i);
  148.             }
  149.  
  150.             if (indexOfNextAdd > to) {
  151. //              System.out.print("...right...");
  152.                 indexOfNextAdd -= to-from+1;
  153.             } else if (indexOfNextAdd >= from) {
  154. //              System.out.println("...mid...");
  155.                 indexOfNextAdd = from;
  156.             }
  157.  
  158. //          System.out.println("new index " + indexOfNextAdd);
  159.         }
  160.     }
  161.  
  162.     /**
  163.      * Set the maximum number of edits this UndoManager will hold. If
  164.      * edits need to be discarded to shrink the limit, they will be
  165.      * told to die in the reverse of the order that they were added.
  166.      *
  167.      * @see #addEdit
  168.      * @see #getLimit
  169.      */
  170.     public synchronized void setLimit(int l) {
  171.         limit = l;
  172.         trimForLimit();
  173.     }
  174.      
  175.  
  176.     /**
  177.      * Returns the the next significant edit to be undone if undo is
  178.      * called. May return null
  179.      */
  180.     protected UndoableEdit editToBeUndone() {
  181.         int i = indexOfNextAdd;
  182.         while (i > 0) {
  183.             UndoableEdit edit = (UndoableEdit)edits.elementAt(--i);
  184.             if (edit.isSignificant()) {
  185.                 return edit;
  186.             }
  187.         }
  188.  
  189.         return null;
  190.     }
  191.  
  192.     /**
  193.      * Returns the the next significant edit to be redone if redo is
  194.      * called. May return null
  195.      */
  196.     protected UndoableEdit editToBeRedone() {
  197.         int count = edits.size();
  198.         int i = indexOfNextAdd;
  199.  
  200.         while (i < count) {
  201.             UndoableEdit edit = (UndoableEdit)edits.elementAt(i++);
  202.             if (edit.isSignificant()) {
  203.                 return edit;
  204.             }
  205.         }
  206.  
  207.         return null;
  208.     }
  209.  
  210.     /**
  211.      * Undoes all changes from indexOfNextAdd to edit. Updates indexOfNextAdd accordingly.
  212.      */
  213.     protected void undoTo(UndoableEdit edit) throws CannotUndoException {
  214.         boolean done = false;
  215.         while (!done) {
  216.             UndoableEdit next = (UndoableEdit)edits.elementAt(--indexOfNextAdd);
  217.             next.undo();
  218.             done = next == edit;
  219.         }
  220.     }
  221.  
  222.     /**
  223.      * Redoes all changes from indexOfNextAdd to edit. Updates indexOfNextAdd accordingly.
  224.      */
  225.     protected void redoTo(UndoableEdit edit) throws CannotRedoException {
  226.         boolean done = false;
  227.         while (!done) {
  228.             UndoableEdit next = (UndoableEdit)edits.elementAt(indexOfNextAdd++);
  229.             next.redo();
  230.             done = next == edit;
  231.         }
  232.     }
  233.  
  234.     /**
  235.      * Undo or redo as appropriate. Suitable for binding to an action
  236.      * that toggles between these two functions. Only makes sense
  237.      * to send this if limit == 1.
  238.      *
  239.      * @see #canUndoOrRedo
  240.      * @see #getUndoOrRedoPresentationName
  241.      */
  242.     public synchronized void undoOrRedo() throws CannotRedoException,
  243.         CannotUndoException {
  244.         if (indexOfNextAdd == edits.size()) {
  245.             undo();
  246.         } else {
  247.             redo();
  248.         }
  249.     }
  250.  
  251.     /**
  252.      * Return true if calling undoOrRedo will undo or redo. Suitable
  253.      * for deciding to enable a command that toggles between the two
  254.      * functions, which only makes sense to use if limit == 1.
  255.      *
  256.      * @see #undoOrRedo
  257.      */
  258.     public synchronized boolean canUndoOrRedo() {
  259.         if (indexOfNextAdd == edits.size()) {
  260.             return canUndo();
  261.         } else {
  262.             return canRedo();
  263.         }
  264.     }
  265.  
  266.     /**
  267.      * If this UndoManager is inProgress, undo the last significant
  268.      * UndoableEdit before indexOfNextAdd, and all insignificant edits back to
  269.      * it. Updates indexOfNextAdd accordingly.
  270.      *
  271.      * <p>If not inProgress, indexOfNextAdd is ignored and super's routine is
  272.      * called.</p>
  273.      *
  274.      * @see CompoundEdit#end
  275.      */
  276.     public synchronized void undo() throws CannotUndoException {
  277.         if (inProgress) {
  278.             UndoableEdit edit = editToBeUndone();
  279.             if (edit == null) {
  280.                 throw new CannotUndoException();
  281.             }
  282.             undoTo(edit);
  283.         } else {
  284.             super.undo();
  285.         }
  286.     }
  287.  
  288.     /**
  289.      * Overridden to preserve usual semantics: returns true if an undo
  290.      * operation would be successful now, false otherwise
  291.      */
  292.     public synchronized boolean canUndo() {
  293.         if (inProgress) {
  294.             UndoableEdit edit = editToBeUndone();
  295.             return edit != null && edit.canUndo();
  296.         } else {
  297.             return super.canUndo();
  298.         }
  299.     }
  300.  
  301.     /**
  302.      * If this UndoManager is inProgress, redoes the last significant
  303.      * UndoableEdit at indexOfNextAdd or after, and all insignificant
  304.      * edits up to it. Updates indexOfNextAdd accordingly.
  305.      *
  306.      * <p>If not inProgress, indexOfNextAdd is ignored and super's routine is
  307.      * called.</p>
  308.      *
  309.      * @see CompoundEdit#end
  310.      */
  311.     public synchronized void redo() throws CannotRedoException {
  312.         if (inProgress) {
  313.             UndoableEdit edit = editToBeRedone();
  314.             if (edit == null) {
  315.                 throw new CannotRedoException();
  316.             }
  317.             redoTo(edit);
  318.         } else {
  319.             super.redo();
  320.         }
  321.     }
  322.  
  323.     /**
  324.      * Overridden to preserve usual semantics: returns true if a redo
  325.      * operation would be successful now, false otherwise
  326.      */
  327.     public synchronized boolean canRedo() {
  328.         if (inProgress) {
  329.             UndoableEdit edit = editToBeRedone();
  330.             return edit != null && edit.canRedo();
  331.         } else {
  332.             return super.canRedo();
  333.         }
  334.     }
  335.  
  336.     /**
  337.      * If inProgress, inserts anEdit at indexOfNextAdd, and removes
  338.      * any old edits that were at indexOfNextAdd or later. The die
  339.      * method is called on each edit that is removed is sent, in the
  340.      * reverse of the order the edits were added. Updates
  341.      * indexOfNextAdd.
  342.      *
  343.      * <p>If not inProgress, acts as a CompoundEdit</p>
  344.      *
  345.      * @see CompoundEdit#end
  346.      * @see CompoundEdit#addEdit
  347.      */
  348.     public synchronized boolean addEdit(UndoableEdit anEdit) {
  349.         // Trim from the indexOfNextAdd to the end, as we'll
  350.         // never reach these edits once the new one is added.
  351.         trimEdits(indexOfNextAdd, edits.size()-1);
  352.  
  353.         super.addEdit(anEdit);
  354.  
  355.         // Maybe super added this edit, maybe it didn't (perhaps
  356.         // an in progress compound edit took it instead. Or perhaps
  357.         // this UndoManager is no longer in progress). So make sure
  358.         // the indexOfNextAdd is pointed at the right place.
  359.         indexOfNextAdd = edits.size();
  360.         
  361.         // Enforce the limit
  362.         trimForLimit();
  363.  
  364.         return true;
  365.     }
  366.  
  367.     /**
  368.      * Return the appropriate name for a command that toggles between
  369.      * undo and redo.  Only makes sense to use such a command if limit
  370.      * == 1 and we're not in progress.
  371.      */
  372.     public synchronized String getUndoOrRedoPresentationName() {
  373.         if (indexOfNextAdd == edits.size()) {
  374.             return getUndoPresentationName();
  375.         } else {
  376.             return getRedoPresentationName();
  377.         }
  378.     }
  379.  
  380.     /**
  381.      * <p>If inProgress, returns getUndoPresentationName of the
  382.      * significant edit that will be undone when undo() is invoked.
  383.      * If there is none, returns AbstractUndoableEdit.UndoName</p>
  384.      * 
  385.      * <p>If not inProgress, acts as a CompoundEdit</p>
  386.      *
  387.      * @see     #undo
  388.      * @see     CompoundEdit#getUndoPresentationName
  389.      */
  390.     public synchronized String getUndoPresentationName() {
  391.         if (inProgress) {
  392.             if (canUndo()) {
  393.                 return editToBeUndone().getUndoPresentationName();
  394.             } else {
  395.                 return AbstractUndoableEdit.UndoName;
  396.             }
  397.         } else {
  398.             return super.getUndoPresentationName();
  399.         }
  400.     }
  401.  
  402.     /**
  403.      * If inProgress, returns getRedoPresentationName of the
  404.      * significant edit that will be redone when redo() is invoked.
  405.      * If there is none, returns AbstractUndoableEdit.RedoName
  406.      * 
  407.      * <p>If not inProgress, acts as a CompoundEdit</p>
  408.      *
  409.      * @see     #redo
  410.      * @see     CompoundEdit#getUndoPresentationName
  411.      */
  412.     public synchronized String getRedoPresentationName() {
  413.         if (inProgress) {
  414.             if (canRedo()) {
  415.                 return ((UndoableEdit)edits.elementAt(indexOfNextAdd)).
  416.                     getRedoPresentationName();
  417.             } else {
  418.                 return AbstractUndoableEdit.RedoName;
  419.             }
  420.         } else {
  421.             return super.getRedoPresentationName();
  422.         }
  423.     }
  424.  
  425.     /**
  426.      * Called by the UndoabledEdit sources this UndoManager listens
  427.      * to. Calls addEdit with e.getEdit().
  428.      *
  429.      * @see #addEdit
  430.      */
  431.     public void undoableEditHappened(UndoableEditEvent e) {
  432.         addEdit(e.getEdit());
  433.     }
  434.  
  435.     public String toString() {
  436.         return super.toString() + " limit: " + limit + 
  437.             " indexOfNextAdd: " + indexOfNextAdd;
  438.     }
  439. }
  440.